package fr.ants.model;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * <p>
 * The Grid is the "World Map", where all nodes are connected.
 * </p>
 * 
 * @author Vianney DUPOY DE GUITARD
 */
public class Grid {

	/** Size of the grid (X). */
	private int sizeX;

	/** Size of the grid (Y). */
	private int sizeY;

	/** The node grid. */
	private Node[][] map;

	/** List of the nests on this grid. */
	private BlockingQueue<Nest> nests = new LinkedBlockingQueue<Nest>();

	/**
	 * Creates an empty grid with nodes.
	 */
	public Grid() {
		map = new Node[sizeX][sizeY];
		for (int x = 0; x < sizeX; x++) {
			for (int y = 0; y < sizeY; y++) {
				map[x][y] = new Node(x, y);
			}
		}
	}

	/**
	 * Creates a grid from values.
	 * 
	 * @param pSizeX : its X size.
	 * @param pSizeY : its Y size.
	 * @param values : its values.
	 */
	public Grid(int pSizeX, int pSizeY, int[][] values) {
		sizeX = pSizeX;
		sizeY = pSizeY;
		map = new Node[sizeX][sizeY];
		for (int y = 0; y < sizeY; y++) {
			for (int x = 0; x < sizeX; x++) {
				map[x][y] = new Node(x, y, values[y][x]);
			}
		}
	}

	/** @return all the nests on this grid. */
	public BlockingQueue<Nest> getNests() {
		return nests;
	}

	/**
	 * Moves an ant to another node.
	 * 
	 * @param src : source node.
	 * @param dest : destination node.
	 * @param ant : the ant to move.
	 */
	public void moveAnt(Node src, Node dest, Ant ant) {
		if (dest.isWalkable()) {
			if (src.getDifficulty() > 0 && ant.getMoveProgress() < 100) {
				ant.setMoveProgress(Node.MAX_DIFFICULTY - src.getDifficulty() + ant.getMoveProgress());
			} else {
				src.getAnts().remove(ant);
				dest.getAnts().add(ant);
				ant.setNode(dest);
			}
		}
	}

	/**
	 * Adds a nest on the grid.
	 * 
	 * @param x : x position.
	 * @param y : y position.
	 * @param nest : the nest to add.
	 */
	public void addNest(Nest nest) {
		map[nest.getNode().getX()][nest.getNode().getY()].setNest(nest);
		nests.add(nest);
	}

	/**
	 * Adds a resource to the grid.
	 * 
	 * @param x : x position.
	 * @param y : y position.
	 * @param res : the resource to add.
	 */
	public void addResource(int x, int y, Resource res) {
		map[x][y].setResource(res);
	}
	
	/**
	 * Adds resources to the grid.
	 * 
	 * @param x : x position.
	 * @param y : y position.
	 * @param res : the resource to add.
	 * @param pSize : number of resources by side.
	 */
	public void addResource(int x, int y, Resource res, int pSize) {
		if (pSize%2 == 0){	
			for (int i= Math.max(x- (pSize/2- 1),0) ;i <= Math.min(x+(pSize/2),sizeX-1);i++ ) {
				for (int j= Math.max(y- (pSize/2- 1),0) ;j <= Math.min(y+(pSize/2),sizeY-1);j++ ) {
					Resource resource = new Resource((int) res.getCapacity(),(int) res.getAmount(), res.getRegenRate());
					map[i][j].setResource(resource);
				}
			}
		}
		
		else {
			for (int i= Math.max(x- (pSize/2),0) ;i <= Math.min(x+(pSize/2),sizeX-1);i++ ) {
				for (int j= Math.max(y- (pSize/2),0) ;j <= Math.min(y+(pSize/2),sizeY-1);j++ ) {
					Resource resource = new Resource((int) res.getCapacity(),(int) res.getAmount(), res.getRegenRate());
					map[i][j].setResource(resource);
				}
			}
			
		}
	}
	/**
	 * Adds a obstacle to the grid.
	 * 
	 * @param x : x position.
	 * @param y : y position.
	 * @param res : the resource to add.
	 */
	public void addObstacle(int x, int y, int pDifficulty) {
		map[x][y].setDifficulty(pDifficulty);
	}
	
	/**
	 * Adds a sized obstacle to the grid.
	 * 
	 * @param x : x position.
	 * @param y : y position.
	 * @param pDifficulty : difficulty of the obstacle
	 * @param pSize : size of the obstacle
	 */
	public void addObstacle(int x, int y, int pDifficulty, int pSize) {
			
		if (pSize%2 == 0){	
			for (int i= Math.max(x- (pSize/2- 1),0) ;i <= Math.min(x+(pSize/2),sizeX-1);i++ ) {
				for (int j= Math.max(y- (pSize/2- 1),0) ;j <= Math.min(y+(pSize/2),sizeY-1);j++ ) {
					map[i][j].setDifficulty(pDifficulty);
				}
			}
		}
		
		else {
			for (int i= Math.max(x- (pSize/2),0) ;i <= Math.min(x+(pSize/2),sizeX-1);i++ ) {
				for (int j= Math.max(y- (pSize/2),0) ;j <= Math.min(y+(pSize/2),sizeY-1);j++ ) {
					map[i][j].setDifficulty(pDifficulty);
				}
			}
			
		}
	}

	/** @return the nodes. */
	public Node[][] getNodes() {
		return map;
	}

	/**
	 * Return the node located at (x, y).
	 * 
	 * @param x : x coordinate.
	 * @param y : y coordinate.
	 * @return the node located at (x, y).
	 */
	public Node getNode(int x, int y) {
		if (x >= sizeX) {
			x = sizeX - 1;
		} else if (x < 0) {
			x = 0;
		}
		if (y >= sizeY) {
			y = sizeY - 1;
		} else if (y < 0) {
			y = 0;
		}
		return map[x][y];
	}

	/**
	 * Return all the neighbors for a node.
	 * 
	 * @param node : the node to "visit".
	 * @return the neighbors.
	 */
	public Set<Node> getNeighbors(Node node) {
		Set<Node> neighbors = new HashSet<>();

		// Grab the potential neighbors.
		Node north = getNode(node.getX(), node.getY() - 1);
		Node south = getNode(node.getX(), node.getY() + 1);
		Node west = getNode(node.getX() - 1, node.getY());
		Node east = getNode(node.getX() + 1, node.getY());

		// Add all the "real" neighbors.
		if (!node.equals(north) && north.isWalkable())
			neighbors.add(north);
		if (!node.equals(south) && south.isWalkable())
			neighbors.add(south);
		if (!node.equals(west) && west.isWalkable())
			neighbors.add(west);
		if (!node.equals(east) && east.isWalkable())
			neighbors.add(east);

		// Return the neighbors.
		return neighbors;
	}

	/** @return the X size of the grid. */
	public int getSizeX() {
		return sizeX;
	}

	/** @return the Y size of the grid. */
	public int getSizeY() {
		return sizeY;
	}

	/** Clears all A Star data. */
	public void clearAStar() {
		for (Node[] row : map) {
			for (Node n : row) {
				n.getAStarData().reset();
			}
		}
	}

	/** Resets the grid. */
	public void reset() {
		for (Node[] row : map) {
			for (Node n : row) {
				n.reset();
			}
		}
	}
	
}